﻿// (c) Norbert Huffschmid
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.

using System;
using System.Configuration.Install;
using System.Management;
using System.Net;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceProcess;
using System.Threading;
using log4net;
using NProtocol.ShoutBoxDiscoveryProtocol;
using ShoutBox.Core;
using ShoutBox.Service.Properties;

namespace ShoutBox.Service
{
    /// <summary>
    /// ShoutBox service
    /// </summary>
    public class ShoutBoxService : ServiceBase
    {
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private IPlatformAdapterAudioOut platformAdapter;
        private ShoutBoxDiscoveryServer shoutBoxDiscoveryServer;
        private ShoutBoxEndPoint shoutBoxEndPoint;
        private ShoutBoxEndPointController shoutBoxEndPointController;
        private ServiceHost controllerHost;


        /// <summary>
        /// Constructor
        /// </summary>
        public ShoutBoxService()
            : base()
        {
            this.CanHandlePowerEvent = true;
            this.CanHandleSessionChangeEvent = true;
            this.CanShutdown = true;
        }


        public static void Main(string[] args)
        {
            try
            {
#if DEBUG
                // for test purposes we can "start" and "stop" the program manually

                ShoutBoxService sbs = new ShoutBoxService();

                sbs.OnStart(args);

                Console.WriteLine("Press s to suspend, c to continue, l for session changes and any other key to terminate ...");
                string input;
                do
                {
                    input = Console.ReadLine();

                    if (input.Equals("s"))
                    {
                        sbs.OnPowerEvent(PowerBroadcastStatus.Suspend);
                    }
                    else if (input.Equals("c"))
                    {
                        sbs.OnPowerEvent(PowerBroadcastStatus.ResumeSuspend);
                    }
                    else if (input.Equals("l"))
                    {
                        SessionChangeDescription scd = new SessionChangeDescription();
                        sbs.OnSessionChange(scd);
                    }
                    else
                        break;
                }
                while (true);

                sbs.OnStop();
                sbs.OnShutdown();
#else
                if (string.Concat(args).Equals("/i"))
                    // install service
                    ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });
                else if (string.Concat(args).Equals("/u"))
                    // uninstall service
                    ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location });
                else
                    // run service
                    ServiceBase.Run(new ShoutBoxService());
#endif
            }
            catch (Exception ex)
            {
                log.Fatal(ex.Message, ex);
            }
        }

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);

            // start shoutbox endpoint in thread for service timeout reasons
            Thread workerThread = new Thread(OnStartWorker);
            workerThread.Name = "ShoutBoxServiceWorkerThread";
            workerThread.IsBackground = false;
            workerThread.Start();
        }

        protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
        {
            log.Info("OnPowerEvent: " + powerStatus.ToString());

            // sleep mode causes sporadic SocketExceptions on RtpEndSystem
            if (powerStatus == PowerBroadcastStatus.Suspend)
                this.shoutBoxEndPoint.StopNetworkServices();
            else if (powerStatus == PowerBroadcastStatus.ResumeSuspend)
                this.shoutBoxEndPoint.StartNetworkServices();

            return base.OnPowerEvent(powerStatus);
        }

        protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {
            log.Info("OnSessionChange - Reason: " + changeDescription.Reason.ToString());

            base.OnSessionChange(changeDescription);

            // determine shoutbox name depending from logged-in user
            ManagementClass computerSystem = new ManagementClass("Win32_ComputerSystem");
            foreach (ManagementObject currentObject in computerSystem.GetInstances())
            {
                if ((currentObject.Properties["UserName"] != null) && (currentObject.Properties["UserName"].Value != null))
                {
                    this.shoutBoxEndPoint.Name = currentObject.Properties["UserName"].Value.ToString();

                    if (this.shoutBoxEndPoint.Name.Contains("\\"))
                    {
                        // strip domain part
                        int domainEndIndex = this.shoutBoxEndPoint.Name.IndexOf("\\");
                        this.shoutBoxEndPoint.Name = this.shoutBoxEndPoint.Name.Substring(domainEndIndex + 1);
                    }
                }
                else if ((currentObject.Properties["Name"] != null) && (currentObject.Properties["Name"].Value != null))
                    this.shoutBoxEndPoint.Name = currentObject.Properties["Name"].Value.ToString();
                else
                    this.shoutBoxEndPoint.Name = "ShoutBox";
            }
        }

        protected override void OnStop()
        {
            log.Info("OnStop");

            base.OnStop();

            ShutDown();
        }

        protected override void OnShutdown()
        {
            log.Info("OnShutDown");

            base.OnShutdown();

            ShutDown();
        }


        private void ShutDown()
        {
            this.platformAdapter.Dispose();
            this.shoutBoxEndPoint.Dispose();
            this.shoutBoxDiscoveryServer.Dispose();
            this.shoutBoxEndPointController.ShutDown();
            this.controllerHost.Close();
        }

        private void OnStartWorker()
        {
            log.Debug("OnStart worker thread started.");

            Settings settings = Properties.Settings.Default;

            IPAddress shoutBoxDiscoveryMulticastGroup = IPAddress.Parse(settings.ShoutBoxDiscoveryMulticastGroup);

            this.platformAdapter = new WindowsWaveOutPlatformAdapter();

            // create shoutbox endpoint on this machine
            this.shoutBoxEndPoint = new ShoutBoxEndPoint(settings.RtpPort,
                settings.RtcpPort, settings.ShoutBoxDiscoveryPort,
                shoutBoxDiscoveryMulticastGroup, settings.ShoutBoxDiscoveryInterval,
                this.platformAdapter);

            // create shoutbox discovery server on this machine
            this.shoutBoxDiscoveryServer =
                new ShoutBoxDiscoveryServer(this.shoutBoxEndPoint.ShoutBoxId, shoutBoxDiscoveryMulticastGroup,
                    settings.ShoutBoxDiscoveryPort, settings.RtpPort, settings.RtcpPort);

            // start shoutbox endpoint
            this.shoutBoxEndPoint.StartNetworkServices();

            // create WCF endpoint for shoutbox client(s) on the same machine
            this.shoutBoxEndPointController = new ShoutBoxEndPointController(this.shoutBoxEndPoint);
            this.controllerHost = new ServiceHost(this.shoutBoxEndPointController, new Uri[] { new Uri("net.pipe://localhost") });
            Binding binding = new NetNamedPipeBinding();
            binding.ReceiveTimeout = TimeSpan.MaxValue; // otherwise we have WCF timeouts at suspend/resume
            controllerHost.AddServiceEndpoint(typeof(IShoutBoxEndPointController), binding, "ShoutBoxEndPointController");
            controllerHost.Open();

            log.Debug("OnStart worker thread ended.");
        }
    }
}
